﻿// first_ffmpeg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <fstream>
using namespace std;

extern "C" { //指定函数是C语言函数，函数名不包含重载标注
//引用ffmpeg头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}


//预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")

int main(int argc, char* argv[])
{
    string filename = "400_300_25_preset";

    //支持两种编码格式
    AVCodecID codec_id = AV_CODEC_ID_H264;
    //AVCodecID codec_id = AV_CODEC_ID_HEVC;
    if (argc > 1)
    {
        string codec = argv[1];
        if (codec == "h265" || codec == "hevc")
        {
            codec_id = AV_CODEC_ID_HEVC;
        }
    }

    if (codec_id == AV_CODEC_ID_H264)
    {
        filename += ".h264";
    }
    else if (codec_id == AV_CODEC_ID_HEVC)
    {
        filename += ".h265";
    }
    //------支持两种编码格式

    //写入文件
    ofstream ofs;
    ofs.open(filename, ios::binary);

    //1 找到编码器 AV_CODEC_ID_HEVC(H265)
    auto codec = avcodec_find_encoder(codec_id);
    if (!codec)
    {
        cerr << "codec not find!" << endl;
        return -1;
    }

    //2 编码上下文
    auto context = avcodec_alloc_context3(codec);
    if (!context)
    {
        cerr << "avcodec_alloc_context3 failed!" << endl;
        return -1;
    }


    //3设定上下文参数
    context->width = 400; //视频宽高
    context->height = 300;

    //pts*time_base = 播放时间(秒), time_base是时间戳的“单位”
    //1/25的time_base适用于25fps视频​​，确保时间戳与帧率严格对应
    context->time_base = { 1,25 }; //分数1/25 {分子，分母}

    //元数据像素格式，编码算法相关
    context->pix_fmt = AV_PIX_FMT_YUV420P;

    //编码线程数，通过调用系统接口获取cpu核心数量
    //(此处先写死)
    context->thread_count = 16;


    //ABR 平均比特率
    int br = 400000; //400kb
    //context->bit_rate = br;

    //CQP 恒定质量 H264 QP范围从0-51
    //x264默认23 效果较好18
    //x265默认28 效果较好25
    //av_opt_set_int(context->priv_data, "qp", 18, 0);

    //恒定比特率（CBR)
    //MP4不支持NAL填充，输出文件必须为(MPEG-2 TS)
    //context->rc_min_rate = br;
    //context->rc_max_rate = br;
    //context->bit_rate = br;
    //context->rc_buffer_size = 2 * br;  // 缓冲区设为2倍
    ////
    //context->rc_initial_buffer_occupancy = context->rc_buffer_size * 3 / 4;
    ////
    //////过长的gop会导致比特率分配不均
    //context->gop_size = 30;
    //context->keyint_min = 30;

    //av_opt_set(context->priv_data, "nal_hrd", "cbr", 0);
    //av_opt_set(context->priv_data, "x264opts", "nal-hrd=cbr:force-cfr=1", 0);
    //av_opt_set(context->priv_data, "mpegts_flags", "resend_headers", 0);  // 对TS流很重要

    //恒定速率因子 CRF
    //QP波动，跟约束编码一起使用，防止码率波动超出解码器缓冲能力
    //约束编码(VBV) Constrained Encoding
    av_opt_set_int(context->priv_data, "crf", 23, 0);
    context->rc_max_rate = br;
    context->rc_buffer_size = br * 2;

    //图像组 一组包含一个IDR SPS PPS 其他是P帧和B帧
    context->gop_size = 6;

    //4打开编码上下文(发生错误概率较大)
    int re = avcodec_open2(context, codec, NULL);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "avcodec_open2 failed!" << buf << endl;
        return -1;
    }

    cout << "avcodec_open2 success!" << endl;

    //创建好AVFrame空间 未压缩数据
    auto frame = av_frame_alloc();
    frame->width = context->width;
    frame->height = context->height;
    frame->format = context->pix_fmt;

    re = av_frame_get_buffer(frame, 0);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "av_frame_get_buffer failed!" << buf << endl;
        return -1;
    }

    //av_packet每一帧空间不确定
    auto pkt = av_packet_alloc();
    //二十秒视频 500帧
    for (int i = 0; i < 500; i++)
    {
        //生成AVFrame数据
        //Y
        for (int y = 0; y < context->height; y++)
        {
            for (int x = 0; x < context->width; x++)
            {
                //设定Y的色彩，等号后面是瞎填的
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        //UV
        for (int y = 0; y < context->height / 2; y++)
        {
            for (int x = 0; x < context->width / 2; x++)
            {
                //设定UV的色彩，等号后面是瞎填的
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i; //显示的时间

        //发送未压缩帧到线程中压缩
        re = avcodec_send_frame(context, frame);
        if (re != 0)
        {
            //发送出错一般不做过多处理，偶尔一帧出错很正常
            break;
        }

        while (re >= 0) //返回多帧
        {
        //接收压缩帧，一般前几次调用返回空(缓冲，立刻返回，编码未完成)
        //编码是在独立的线程中
        //每次调用会重新分配pkt中的空间
            re = avcodec_receive_packet(context, pkt);
            if (re == AVERROR(EAGAIN) || re == AVERROR_EOF)
            {
                //AVERROR(EAGAIN)输出不可用，需要再次发送输入内容
                //AVERROR_EOF读到文件结尾
                break;
            }
            if (re < 0)
            {
                char buf[1024] = { 0 };
                av_strerror(re, buf, sizeof(buf) - 1);
                cerr << "avcodec_receive_packet failed!" << buf << endl;
                return -1;
                break;
            }
            //cout << pkt->size << " " << flush;

            //分析NALU
            //一个AVPacket中包含多个NALU 以0001间隔
            //固定结构：0001[NAL_HEAD]
            //1个字节 前三位丢掉，直接取nal_unit_type(5bit)
            //1普通帧
            //5IDR帧
            //7序列参数集SPS
            //8图像参数集PPS
            int nal_unit_type = 0;
            unsigned char nal_head = *(pkt->data + 4); //+4 去掉开头0001
            nal_unit_type = nal_head & 0x1f; //取后5位 0001 1111
            cout << nal_unit_type << " " << flush;
            for (int i = 4; i < pkt->size - 4; i++) //一个data包含多条NALU
            {
                if (pkt->data[i] == 0 &&
                    pkt->data[i+1] == 0 &&
                    pkt->data[i+2] == 1
                    ) //001
                {
                    nal_unit_type = pkt->data[i + 3] & 0x1f;
                    cout << "(" << nal_unit_type << ")" << flush;
                }
            }

            //------分析NALU
            //写入文件
            ofs.write((char*)pkt->data, pkt->size);

            //引用计数-1，释放av_packet指向的buffer空间
            av_packet_unref(pkt);

            //------while (re >= 0)
        }
        

        //------for (int i = 0; i < 250; i++)
    }
    
    ofs.close();
    av_packet_free(&pkt);

    av_frame_free(&frame);
    //释放上下文
    avcodec_free_context(&context);

    //cout << "first ffmpeg" << endl;
    //cout << avcodec_configuration() << endl;
    return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
